﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;

using agree.schema;

using alib.Debugging;
using alib.Enumerable;

namespace agree
{
	[StructLayout(LayoutKind.Explicit), DebuggerDisplay("[{i_start} {i_end}]")]
	public struct ChartSpan : IProximalContext<ChartSpan>, IEquatable<ChartSpan>, IComparable<ChartSpan>
	{
		public unsafe ChartSpan(int i_start, int i_end)
		{
			this.i_start = (ushort)i_start;
			this.i_end = (ushort)i_end;
		}
		public ChartSpan(int i_start, uint length)
			: this(i_start, i_start + (int)length - 1)
		{
		}
		public ChartSpan(ISourceWord wt)
			: this((int)P.SourceCharacterStart.GetValue(wt), (int)P.SourceCharacterEnd.GetValue(wt) - 1)
		{
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never), FieldOffset(0)]
		readonly ushort i_start;
		[DebuggerBrowsable(DebuggerBrowsableState.Never), FieldOffset(2)]
		readonly ushort i_end;

		public int Length { get { return i_end - i_start + 1; } }
		public int CoverageCount { get { return i_end - i_start + 1; } }
		public int StartIndex { get { return i_start; } }
		public int EndIndex { get { return i_end; } }

		public int NotificationIndex(bool f_right)
		{
			return f_right ? (i_end << 1) + 1 : (i_start - 1) << 1;
		}

		public bool CanCombineWith(ChartSpan other)
		{
			if (i_start != other.i_start)
			{
				if (i_start < other.i_start)
				{
					if (i_end < other.i_start)
						return true;
				}
				else
				{
					if (other.i_end < i_start)
						return true;
				}
			}
			return false;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// only returns true mutual exclusion, not abutment
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public bool CombineWith(ChartSpan other, out ChartSpan result)
		{
			int ois;
			if (i_start != (ois = other.i_start))
			{
				if (i_start < ois)
				{
					result = new ChartSpan(i_start, other.i_end);
					if (i_end < ois)
						return true;
				}
				else
				{
					result = new ChartSpan(ois, i_end);
					if (other.i_end < i_start)
						return true;
				}
			}
			result = default(ChartSpan);
			return false;
		}

		public bool Overlaps(ChartSpan other)
		{
			return (other.i_start <= this.i_end && this.i_end <= other.i_end) ||
				   (this.i_start <= other.i_end && other.i_end <= this.i_end);
		}

		/// <summary>
		/// The range of indexes used by the Span
		/// </summary>
		public IEnumerable<int> Range
		{
			get { for (int i = i_start; i <= i_end; i++) yield return i; }
		}

		public bool IsValid
		{
			get { return i_end >= i_start && i_start >= 0; }
		}

		public override string ToString()
		{
			return String.Format("[{0} {1}]", i_start, i_end);
		}
#if DEBUG
		public string BaString() { return ToString(); }
#endif
		public bool Equals(ChartSpan other)
		{
			return i_start == other.i_start && i_end == other.i_end;
		}
		public static bool operator ==(ChartSpan a, ChartSpan b)
		{
			return a.i_start == b.i_start && a.i_end == b.i_end;
		}
		public static bool operator !=(ChartSpan a, ChartSpan b)
		{
			return a.i_start != b.i_start || a.i_end != b.i_end;
		}
		public override int GetHashCode()
		{
			return (i_start << 16) | i_end;
		}
		public override bool Equals(object obj)
		{
			return obj is ChartSpan && this.Equals((ChartSpan)obj);
		}

		public int CompareTo(ChartSpan other)
		{
			int ix = i_start - other.i_start;
			if (ix != 0)
				return ix;
			return i_end - other.i_end;
		}

		public static IEnumerable<ChartSpan> FromIndexList(IEnumerable<int> list)
		{
			var ie = list.OrderByDistinct().GetEnumerator();
			if (!ie.MoveNext())
				yield break;
			int i_start = ie.Current;
			int i_prev = i_start;
			while (ie.MoveNext())
			{
				int i = ie.Current;
				if (i != i_prev + 1)
				{
					yield return new ChartSpan(i_start, i_prev);
					i_start = i;
				}
				i_prev = i;
			}
			yield return new ChartSpan(i_start, i_prev);
		}
	};
}
